﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Data;
using Boggle.Net.Model;
using Caliburn.Micro;

namespace Boggle.Net.ViewModels
{
    public sealed class GameViewModel : PropertyChangedBase, IDisposable
    {
        private readonly TimeSpan _timerLimit = TimeSpan.FromMinutes(3.01);

        private readonly TrayViewModel _tray;

        private readonly ObservableCollection<AcceptablePlay> _wordList;
        private readonly CollectionView _filteredWordList;

        private AcceptablePlay _matchingWord;
        private IDisposable _searchTask;
        private bool _isFinished;
        private int _score;

        public GameViewModel(Tray tray)
        {
            _tray = new TrayViewModel(tray);
            _tray.Selection.Changed += OnSelectionOnChanged;

            _wordList = new ObservableCollection<AcceptablePlay>();
            _filteredWordList = new ListCollectionView(_wordList) { Filter = FilterWord };

            GameTime = _timerLimit;

            Timer = IoC.Get<IStopwatch>();
            Timer.Tick += delegate { UpdateTimeRemaining(); };

            var startingTime = DateTime.Now;

            _searchTask = IoC.Get<ISolver>().FindAllWordsIn(tray).Subscribe(acceptablePlay => _wordList.Add(acceptablePlay),
                () =>
                {
                    SearchStatistics = new StatisticsViewModel(_wordList, DateTime.Now - startingTime);
                    NotifyOfPropertyChange(() => SearchStatistics);

                    Timer.IsRunning = true;
                });               
        }

        public void Dispose()
        {
            CancelSearch();

            if (Timer != null)
            {
                Timer.Dispose();
                Timer = null;
            }
        }

        public int Score
        {
            get { return _score; }
            set
            {
                _score = value;
                NotifyOfPropertyChange(() => Score);
            }
        }

        public TimeSpan GameTime
        {
            get;
            private set;
        }

        public TrayViewModel Tray
        {
            get { return _tray; }
        }

        public StatisticsViewModel SearchStatistics
        {
            get; 
            private set;
        }

        public IEnumerable<AcceptablePlay> FoundPlays
        {
            get { return _wordList.Where(play => play.WasFound); }
        }

        public object WordList
        {
            get { return _filteredWordList; }
        }

        public IEnumerable<string> DistinctWordList
        {
            get { return _wordList.Select(result => result.Word).Distinct(); }
        }

        public bool IsFinished
        {
            get { return _isFinished; }
            set
            {
                if (value == IsFinished) return;

                _isFinished = value;

                NotifyOfPropertyChange(() => IsFinished);
                NotifyOfPropertyChange(() => CanSolve);

                UpdateWordList();
            }
        }

        #region Command Handlers

        public bool CanSolve
        {
            get { return !IsFinished; }
        }

        public void Solve()
        {
            if (!CanSolve) return;

            Timer.IsRunning = false;
            Tray.IsEnabled = false;

            IsFinished = true;

            ClearSelection();
        }

        public bool CanAddSelection
        {
            get { return !IsFinished && _matchingWord != null && !_matchingWord.WasFound; }
        }

        public void AddSelection()
        {
            if (!CanAddSelection) return;

            _matchingWord.WasFound = true;

            Score += _matchingWord.Score;

            ClearSelection();
            UpdateWordList();

            IsFinished = SearchStatistics != null && FoundPlays.Count() == SearchStatistics.TotalDistinctWords;
        }

        public bool CanClearSelection
        {
            get { return !Tray.Selection.IsEmpty; }
        }

        public void ClearSelection()
        {
            Tray.Selection.Clear();
        }

        #endregion

        private IStopwatch Timer
        {
            get;
            set;
        }

        private void CancelSearch()
        {
            if (_searchTask == null) return;

            _searchTask.Dispose();
            _searchTask = null;
        }

        private bool FilterWord(object item)
        {
            return IsFinished || ((AcceptablePlay)item).WasFound;
        }

        private AcceptablePlay FindPlayThatMatchesSelection()
        {
            if (IsFinished || Tray.Selection.Count() < Game.MinimumWordLength) return null;

            var wordBeingBuilt = _tray.SelectedWord;
            var candidates = _wordList.Where(result => result.Word == wordBeingBuilt).ToArray();

            if (candidates.Any(result => result.WasFound)) return null;

            var path = Tray.SelectedPath;
            return candidates.FirstOrDefault(result => result.Path.SequenceEqual(path));
        }

        private void UpdateWordList()
        {
            _filteredWordList.Refresh();
        }

        private void UpdateTimeRemaining()
        {
            var timeLeft = _timerLimit - Timer.TotalRunningTime;
            var finished = timeLeft <= TimeSpan.Zero;

            GameTime = finished ? TimeSpan.Zero : timeLeft;
            NotifyOfPropertyChange(() => GameTime);

            if (finished) Solve();
        }

        private void OnSelectionOnChanged(object sender, EventArgs e)
        {
            _matchingWord = FindPlayThatMatchesSelection();

            NotifyOfPropertyChange(() => CanAddSelection);
            NotifyOfPropertyChange(() => CanClearSelection);
        }
    }
}
